package com.svm.operators;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;

import libsvm.svm;
import libsvm.svm_model;
import libsvm.svm_node;
import libsvm.svm_parameter;
import libsvm.svm_problem;

import com.svm.bean.Image;
import com.svm.bean.Parameter;
import com.svm.bean.SvmClass;
import com.svm.util.SvmClassUtils;
import com.svm.util.SystemConfig;

public class Trainning {

	private svm_parameter parameters;
	private svm_problem problem;
	private svm_model model;

	private String errorMsg;
	private BufferedReader featureFile;
	private Parameter parameter;
	private List<SvmClass> classes;

	public Trainning(List<SvmClass> toBeTrained, Parameter parameter) {
		this.parameter = parameter;
		this.classes = toBeTrained;
	}

	public void train() {

		for (SvmClass s : this.classes) {
			this.run(s);
		}
	}

	private void run(SvmClass svmClass) {

		try {

			String trainningFile = null;

			this.setParameters();

			if (this.parameter.getTrainningMode() == 'n') {

				trainningFile = SystemConfig.SVM_FILES_REPOSITORY
						+ svmClass.getName().toLowerCase() + ".train";

				svmClass.setTrainningFile(trainningFile);

			} else if (this.parameter.getTrainningMode() == 's') {

				trainningFile = SystemConfig.SVM_SUB_FILES_REPOSITORY
						+ svmClass.getName().toLowerCase() + ".train";

				svmClass.setSubTrainningFile(trainningFile);

			}

			this.oneAgainstAll(svmClass);
			this.readProblem();

			this.errorMsg = svm.svm_check_parameter(this.problem,
					this.parameters);

			this.model = svm.svm_train(this.problem, this.parameters);
			svm.svm_save_model(trainningFile, this.model);

			SvmClassUtils.saveSvmClass(svmClass);

			if (this.errorMsg != null) {
				System.out.println(this.errorMsg);
			}

		} catch (IOException ex) {
			System.out.println(ex);
		}

	}

	private void oneAgainstAll(SvmClass svmClass) {

		try {
			String fileName = null;

			if (this.parameter.getTrainningMode() == 'n') {

				fileName = SystemConfig.CLASS_FILES_REPOSITORY
						+ svmClass.getName().toLowerCase() + ".fet";

			} else if (this.parameter.getTrainningMode() == 's') {

				fileName = SystemConfig.CLASS_SUB_FILES_REPOSITORY
						+ svmClass.getName().toLowerCase() + ".fet";

			}

			BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));

			for (SvmClass c : this.classes) {

				if (!c.getName().equals(svmClass.getName())) {

					// APLICACAO DA FORMULA 2K/N-1
					int max = (2 * c.getImages().size())
							/ (this.classes.size() - 1);

					if (max > c.getImages().size()) {
						max = c.getImages().size();
					}

					Collections.shuffle(c.getImages());

					for (int i = 0; i < max; i++) {
						BufferedReader file = new BufferedReader(
								new FileReader(c.getImages().get(i)
										.getFeatureFilePath().trim()));

						while (file.ready()) {
							String line = "-1,";
							line += file.readLine();
							writer.write(line);
							writer.newLine();

						}

						file.close();
					}
				} else {

					for (Image img : c.getImages()) {
						BufferedReader file = new BufferedReader(
								new FileReader(img.getFeatureFilePath().trim()));

						while (file.ready()) {
							String line = "1,";
							line += file.readLine();
							writer.write(line);
							writer.newLine();

						}
						file.close();
					}
				}
			}
			writer.close();
			this.featureFile = new BufferedReader(new FileReader(fileName));

		} catch (IOException ex) {
			System.out.println(ex);
		}

	}

	private void setParameters() {

		this.parameters = new svm_parameter();

		// default values
		this.parameters.svm_type = svm_parameter.C_SVC;
		this.parameters.kernel_type = svm_parameter.RBF;
		this.parameters.degree = 3;
		this.parameters.gamma = 0; // 1/num_features
		this.parameters.coef0 = 0;
		this.parameters.nu = 0.5;
		this.parameters.cache_size = 100;
		this.parameters.C = 1;
		this.parameters.eps = 1e-3;
		this.parameters.p = 0.1;
		this.parameters.shrinking = 1;
		this.parameters.probability = 0;
		this.parameters.nr_weight = 0;
		this.parameters.weight_label = new int[0];
		this.parameters.weight = new double[0];

		// pode variar
		this.parameters.svm_type = 3;
		this.parameters.kernel_type = 1;
		this.parameters.probability = 1;

	}

	private void readProblem() {

		try {

			Vector<Double> vy = new Vector<Double>();
			Vector<svm_node[]> vx = new Vector<svm_node[]>();
			int max_index = 0;
			String line;

			while (this.featureFile.ready()) {

				line = this.featureFile.readLine();

				StringTokenizer caracteristicas = new StringTokenizer(line, ",");

				vy.addElement(Double.parseDouble(caracteristicas.nextToken()));

				int qtde = caracteristicas.countTokens() / 2;

				svm_node[] node = new svm_node[qtde];

				for (int j = 0; j < qtde; j++) {
					node[j] = new svm_node();
					node[j].index = Integer.parseInt(caracteristicas
							.nextToken().trim());
					node[j].value = Double.parseDouble(caracteristicas
							.nextToken().trim());
				}

				if (qtde > 0)
					max_index = Math.max(max_index, node[qtde - 1].index);
				vx.addElement(node);
			}

			this.problem = new svm_problem();
			this.problem.l = vy.size();
			this.problem.x = new svm_node[this.problem.l][];

			for (int i = 0; i < this.problem.l; i++)
				this.problem.x[i] = vx.elementAt(i);

			this.problem.y = new double[problem.l];

			for (int i = 0; i < this.problem.l; i++)
				this.problem.y[i] = vy.elementAt(i);

			if (this.parameters.gamma == 0 && max_index > 0)
				this.parameters.gamma = 1.0 / max_index;

			if (this.parameters.kernel_type == svm_parameter.PRECOMPUTED)

				for (int i = 0; i < this.problem.l; i++) {

					if (this.problem.x[i][0].index != 0) {
						System.err
								.print("Wrong kernel matrix: first column must be 0:sample_serial_number\n");
						System.exit(1);
					}
					if ((int) this.problem.x[i][0].value <= 0
							|| (int) this.problem.x[i][0].value > max_index) {
						System.err
								.print("Wrong input format: sample_serial_number out of range\n");
						System.exit(1);
					}
				}

		} catch (IOException ex) {

			System.out.println(ex);

		} finally {

			try {

				this.featureFile.close();

			} catch (IOException e) {
				System.out.println(e);
			}

		}

	}

}
